package com.hzw.core.store

import android.content.Context
import androidx.datastore.core.DataStore
import androidx.datastore.preferences.core.*
import androidx.datastore.preferences.preferencesDataStore
import com.hzw.core.ctx.appCtx
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*

private val Context.preferences: DataStore<Preferences> by preferencesDataStore("hzw_preferences")

object DataPreferences {

    private val keyMap = mutableMapOf<String, Preferences.Key<*>>()

    private val prefScope = CoroutineScope(SupervisorJob() + Dispatchers.IO)

    private val preferences: DataStore<Preferences> = appCtx.preferences
    private val preferencesFlow = preferences.data.shareIn(prefScope, SharingStarted.Eagerly, 1)

    private fun <T> putData(key: Preferences.Key<T>, value: T?) {
        prefScope.launch {
            preferences.edit {
                if (value != null) it[key] = value else it.remove(key)
            }
        }
    }

    private fun <T> readNullableData(key: Preferences.Key<T>, defValue: T?): T? {
        return runBlocking(prefScope.coroutineContext) {
            preferencesFlow.map { it[key] ?: defValue }.firstOrNull()
        }
    }

    private fun <T> readNonNullData(key: Preferences.Key<T>, defValue: T): T {
        return runBlocking(prefScope.coroutineContext) {
            preferencesFlow.map { it[key] ?: defValue }.first()
        }
    }

    fun <T> readData(key: Preferences.Key<T>): Flow<T?> = preferencesFlow.map { it[key] }

    fun putString(keyName: String, value: String?) {
        val key = getStringKey(keyName)
        putData(key, value)
    }

    private fun getStringKey(keyName: String): Preferences.Key<String> {
        val oldKey = keyMap[keyName]
        return if (oldKey == null) {
            stringPreferencesKey(keyName)
        } else {
            oldKey as Preferences.Key<String>
        }
    }

    fun putStringSet(keyName: String, value: Set<String>?) {
        putData(getStringSetKey(keyName), value)
    }

    private fun getStringSetKey(
        keyName: String
    ): Preferences.Key<Set<String>> {
        val oldKey = keyMap[keyName]
        return if (oldKey == null) {
            stringSetPreferencesKey(keyName)
        } else {
            oldKey as Preferences.Key<Set<String>>
        }
    }

    fun putInt(keyName: String, value: Int) {
        putData(getIntKey(keyName), value)
    }

    private fun getIntKey(keyName: String): Preferences.Key<Int> {
        val oldKey = keyMap[keyName]
        return if (oldKey == null) {
            intPreferencesKey(keyName)
        } else {
            oldKey as Preferences.Key<Int>
        }
    }

    fun putLong(keyName: String, value: Long) {
        putData(getLongKey(keyName), value)
    }

    private fun getLongKey(keyName: String): Preferences.Key<Long> {
        val oldKey = keyMap[keyName]
        return if (oldKey == null) {
            longPreferencesKey(keyName)
        } else {
            oldKey as Preferences.Key<Long>
        }
    }

    fun putFloat(keyName: String, value: Float) {
        putData(getFloatKey(keyName), value)
    }

    private fun getFloatKey(
        keyName: String
    ): Preferences.Key<Float> {
        val oldKey = keyMap[keyName]
        return if (oldKey == null) {
            floatPreferencesKey(keyName)
        } else {
            oldKey as Preferences.Key<Float>
        }
    }

    fun putBoolean(keyName: String, value: Boolean) {
        putData(getBooleanKey(keyName), value)
    }

    private fun getBooleanKey(keyName: String): Preferences.Key<Boolean> {
        val oldKey = keyMap[keyName]
        return if (oldKey == null) {
            booleanPreferencesKey(keyName)
        } else {
            oldKey as Preferences.Key<Boolean>
        }
    }

    fun getString(key: String, defValue: String = ""): String = readNonNullData(stringPreferencesKey(key), defValue)

    fun getStringFlow(keyName: String): Flow<String?> = readData(getStringKey(keyName))

    fun getStringSet(key: String, defValues: Set<String>?): Set<String>? = readNullableData(stringSetPreferencesKey(key), defValues)

    fun getStringSetFlow(key: String) = readData(getStringSetKey(key))

    fun getInt(key: String, defValue: Int = -1): Int = readNonNullData(intPreferencesKey(key), defValue)

    fun getIntFlow(key: String) = readData(getIntKey(key))

    fun getLong(key: String, defValue: Long = -1L): Long = readNonNullData(longPreferencesKey(key), defValue)

    fun getLongFlow(key: String) = readData(getLongKey(key))

    fun getFloat(key: String, defValue: Float = -1f): Float = readNonNullData(floatPreferencesKey(key), defValue)

    fun getFloatFlow(key: String) = readData(getFloatKey(key))

    fun getBoolean(key: String, defValue: Boolean = false): Boolean = readNonNullData(booleanPreferencesKey(key), defValue)

    fun getBooleanFlow(key: String) = readData(getBooleanKey(key))
}
